﻿using System;
using System.Drawing;
using System.Windows.Forms;

using DimsMapper.Modules;
using DimsMapper.UIClasses;

using Microsoft.DirectX.DirectDraw;

namespace DimsMapper
{
    static class MapDraw
    {
        enum TileCorner : int
        {
            TILE_LEFT_CORNER_X  = 0,
            TILE_LEFT_CORNER_Y  = 11,

            TILE_UP_CORNER_X    = 47,
            TILE_UP_CORNER_Y    = 0,

            TILE_RIGHT_CORNER_X = 79,
            TILE_RIGHT_CORNER_Y = 23,

            TILE_DOWN_CORNER_X  = 35,
            TILE_DOWN_CORNER_Y  = 35,
        }

        private static Device device;
        private static Surface primary;

        private static Surface selectedBox;
        private static int selectedBoxW = -1;
        private static int selectedBoxH;

        public static void DirectDrawInit(Control win)
        {
            //PresentParameters presentParams = new PresentParameters();
            //presentParams.Windowed = true;
            //presentParams.SwapEffect = SwapEffect.Discard;
            //presentParams.AutoDepthStencilFormat = DepthFormat.D16;
            //presentParams.EnableAutoDepthStencil = true;
            //device = new Device(0, DeviceType.Hardware, null, CreateFlags.HardwareVertexProcessing, presentParams);

            if (device != null) return;

            device = new Device();
            device.SetCooperativeLevel(win, CooperativeLevelFlags.Normal);

            SurfaceDescription description = new SurfaceDescription();
            description.SurfaceCaps.PrimarySurface = true;
            primary = new Surface(description, device);
        }

        public static Clipper CreatePrimarySurface(Control mapViewPort)
        {
            Clipper mapClipper = new Clipper(device);
            mapClipper.Window = mapViewPort;
            return mapClipper;
        }

        public static Surface CreateSurface(int w, int h)
        {
            SurfaceDescription description = new SurfaceDescription();
            description.SurfaceCaps.OffScreenPlain = true;
            description.Width = w;
            description.Height = h;

            Surface surface = new Surface(description, device);

            //Rectangle[] clpList = new Rectangle[1];
            //clpList[0] = new Rectangle(0, 0, w, h);

            //Clipper clipper = new Clipper(device);
            //clipper.ClipList = clpList;

            //surface.Clipper = clipper;
            return surface;
        }

        public static Surface CreateSpriteSurface(int w, int h)
        {
            SurfaceDescription description = new SurfaceDescription();
            description.SurfaceCaps.OffScreenPlain = true;
            description.SurfaceCaps.SystemMemory = true;
            description.Width = w;
            description.Height = h;

            Surface surface = new Surface(description, device);

            ColorKey cKey = new ColorKey();
            cKey.ColorSpaceLowValue = 255;
            cKey.ColorSpaceHighValue = 255;
            surface.SetColorKey(ColorKeyFlags.SourceDraw, cKey);

            return surface;
        }

        public static void RepaintDDraw(Control win, Surface surface, Clipper clipper)
        {
            Rectangle crs = win.ClientRectangle;
            Rectangle crd = win.RectangleToScreen(crs);

            if (!primary.IsLost) {
                primary.Restore();
                if (!surface.IsLost) {
                    surface.Restore();
                }
            }
            primary.Clipper = clipper;
            primary.Draw(crd, surface, crs, DrawFlags.Wait);
        }

        private static void DrawSprite(Surface scr, int spriteX, int spriteY, int w, int h, int x, int y, Surface surface)
        {
            int offx = 0;
            if (x < 0) {
                offx = -x;
                if (offx >= w) return;
                x = 0;
            }

            int offy = 0;
            if (y < 0) {
                offy = -y;
                if (offy >= h) return;
                y = 0;
            }

            int xw = (x + w) - surface.SurfaceDescription.Width;
            if (xw > 0) w -= xw;

            int yh = (y + h) - surface.SurfaceDescription.Height;
            if (yh > 0) h -= yh;

            Rectangle sRect = new Rectangle(spriteX + offx, spriteY + offy, w - offx, h - offy);
            surface.DrawFast(x, y, scr, sRect, DrawFastFlags.Wait | DrawFastFlags.SourceColorKey);
        }

        private static void DrawSprite(FrmImage frm, int dir, int frame, int x, int y, Surface surface)
        {
            int w = frm.GetWidth(dir, frame);
            int h = frm.GetHeight(dir, frame);
            
            DrawSprite(frm.GetSprite(), frm.SpriteX(dir, frame), frm.SpriteY(dir, frame), w, h, x, y, surface);

            //Rectangle sRect = new Rectangle(frm.SpriteX(dir, frame), frm.SpriteY(dir, frame), w, h);
            //surface.Draw(new Rectangle(x, y, w, h), frm.GetSprite(), sRect, DrawFlags.Wait | DrawFlags.KeySource);
        }

        private static void DrawPoligon(int x1, int y1, int x2, int y2, Color clrPen, int wPen, Surface surface, int worldX, int worldY, bool roof = false)
        {
            surface.ForeColor = clrPen;
            surface.DrawWidth = wPen;

            int v1x, v1y, v2x, v2y, v3x, v3y, v4x, v4y;

            Utilites.GetTileWorldCoord(x1, y1, out v1x, out v1y);
            Utilites.GetTileWorldCoord(x1, y2, out v2x, out v2y);
            Utilites.GetTileWorldCoord(x2, y2, out v3x, out v3y);
            Utilites.GetTileWorldCoord(x2, y1, out v4x, out v4y);

            v1x += (int)TileCorner.TILE_UP_CORNER_X - worldX;
            v1y += (int)TileCorner.TILE_UP_CORNER_Y - worldY;

            v2x += (int)TileCorner.TILE_RIGHT_CORNER_X - worldX;
            v2y += (int)TileCorner.TILE_RIGHT_CORNER_Y - worldY;

            v3x += (int)TileCorner.TILE_DOWN_CORNER_X - worldX;
            v3y += (int)TileCorner.TILE_DOWN_CORNER_Y - worldY;

            v4x += (int)TileCorner.TILE_LEFT_CORNER_X - worldX;
            v4y += (int)TileCorner.TILE_LEFT_CORNER_Y - worldY;
            
            surface.DrawLine(v4x, v4y, v1x, v1y); // верхняя линия
            surface.DrawLine(v1x, v1y, v2x, v2y); // правая линия
            surface.DrawLine(v2x, v2y, v3x, v3y); // нижняя линия
            surface.DrawLine(v3x, v3y, v4x, v4y); // левая линия

            if (roof) {
                surface.DrawLine(v4x, v4y, v4x, v4y + 96);
                surface.DrawLine(v1x, v1y, v1x, v1y + 96);
                surface.DrawLine(v2x, v2y, v2x, v2y + 96);
                surface.DrawLine(v3x, v3y, v3x, v3y + 96);
            }
        }

        private static void DrawSelectRectangle(int x1, int y1, int x2, int y2, Color clrPen, int wPen, Surface surface)
        {
            surface.ForeColor = clrPen;
            surface.DrawWidth = wPen;

            surface.DrawBox(x1, y1, x2, y2);
        }

        private static void DrawSelectedBorder(int x, int y, int w, int h, Color clrPen, int wPen, Surface surface)
        {
            // recreate border
            if (selectedBoxW != w || selectedBoxH != h) {
                selectedBoxW = w;
                selectedBoxH = h;

                selectedBox = CreateSpriteSurface(w, h);
                selectedBox.ColorFill(Color.Blue);

                DrawSelectRectangle(0, 0, w, h, clrPen, wPen, selectedBox);
            }
            DrawSprite(selectedBox, 0, 0, w, h, x, y, surface);
        }

        private static void DrawSelectTilePoligon(int x1, int y1, int x2, int y2, Color color, bool roof, MapTab map, Surface surface)
        {
            int x = Math.Min(x1, x2);
            int y = Math.Min(y1, y2);
            int sx = Math.Max(x1, x2);
            int sy = Math.Max(y1, y2);

            if (map.ShowObjects(Main.ShowTypes.RoofTiles)) roof = false;

            DrawPoligon(x, y, sx, sy, color, 1, surface, map.cWorldX, map.cWorldY, roof);
        }

        private static void DrawObjectsLayers(MapTab map, Surface surface)
        {
            RedrawObjects(map, surface, 2); // сначало рисуем плоские
            RedrawObjects(map, surface, 1); // рисуем слой плоскиx предметов 
            RedrawObjects(map, surface, 0); // рисуем остальные (не плоские) объекты
        }

        public static void DrawMap(MapTab map, Surface surface)
        {
            if (!surface.IsLost) surface.Restore();

            surface.ColorFill(Color.FromArgb(64, 64, 64));

            // нарисовать границы карты
            DrawPoligon(0, 0, 99, 99, Color.Red, 2, surface, map.cWorldX, map.cWorldY);

            RedrawTiles(map, surface); // floor

            if (Main.showOnTopBlockers) {
                DrawObjectsLayers(map, surface);
                DrawBlockingHex(map, surface);
            } else {
                DrawBlockingHex(map, surface);
                DrawObjectsLayers(map, surface);
            }

            if (map.ShowObjects(Main.ShowTypes.RoofTiles)) RedrawTiles(map, surface, true); // roof

            if (map.pickObjectPid != -1 && map.mouseOverMap) {
                DrawPickObject(map, surface);
            }
            else if (map.selectedObjectMode || MapTab.selectedTileRegion != 0) {
                if (Main.selectMode != 0) {
                    bool roof = Main.selectMode == 2;
                    DrawSelectTilePoligon(MapTab.startSelectTileX, MapTab.startSelectTileY, MapTab.endSelectTileX, MapTab.endSelectTileY, (roof) ? Color.Yellow : Color.GreenYellow, roof, map, surface);
                } else {
                    DrawSelectRectangle(MapTab.startSelectMouseX, MapTab.startSelectMouseY, MapTab.mouseX, MapTab.mouseY, Color.Red, 1, surface);
                }
            }
        }

        private static void RedrawTiles(MapTab map, Surface surface, bool roof = false)
        {
            int viewW = map.cWorldX + MapTab.cMapViewPortW;
            int viewH = map.cWorldY + MapTab.cMapViewPortH;

            if (map.ShowObjects(Main.ShowTypes.Tiles) || roof) {
                // границы карты в пихселях
                int xx = 4752; // 99*48
                int yy = (roof) ? -96 : 0;
                int prev_xx, prev_yy;

                int totalTiles = LstFiles.FrmTotalCount(Object.Types.Tiles);

                for (int y = 0 ; y < 100; y++)
                {
                    prev_xx = xx;
                    prev_yy = yy;

                    for (int x = 0; x < 100; x++)
                    {
                        int index = (roof) ? map.editedMap.Tiles.GetRoofID(x, y, map.currentLevel) : map.editedMap.Tiles.GetFloorID(x, y, map.currentLevel);
                        index &= 0x3FFF;
                        if (index > 1 && index < totalTiles) {
                            FrmImage frm = FrmFiles.GetFRM(Object.Types.Tiles, index);

                            int w = frm.GetWidth(0, 0);
                            if ((xx + w) > map.cWorldX && xx < viewW) {
                                int h = frm.GetHeight(0, 0);
                                if ((yy + h) > map.cWorldY && yy < viewH) {
                                    DrawSprite(frm, 0, 0, xx - map.cWorldX, yy - map.cWorldY, surface); // рисуем плитку
                                }
                            }
                        }
                        xx -= 48;
                        yy += 12;
                    }
                    xx = prev_xx + 32;
                    yy = prev_yy + 24;
                }
            }

            // Hex Grid
            if (!roof && map.ShowObjects(Main.ShowTypes.HexGrid)) {
                int mapX, mapY;

                for (int y = 0; y < 200; y++)
                {
                    for (int x = 0; x < 200; x++)
                    {
                        Utilites.GetHexWorldCoord(x, y, out mapX, out mapY);
                        if ((mapX + 32) > map.cWorldX && mapX < viewW && (mapY + 16) > map.cWorldY && mapY < viewH) {
                            DrawSprite(FrmFiles.GetFRM(Object.Types.Interface, (int)Main.ShowTypes.HexGrid), 0, 0, mapX - map.cWorldX - 16, mapY - map.cWorldY - 8, surface);
                        }
                    }
                }
            }
        }

        private static void DrawBlockingHex(MapTab map, Surface surface)
        {
            if (!map.ShowObjects(Main.ShowTypes.HexGrid) && !map.ShowObjects(Main.ShowTypes.HexBlock)) return;

            int prevTile = -1;
            int prevBlock = 3;
            int x = 0, y = 0;  
            FrmImage frmHex = null;

            foreach (var obj in map.editedMap.Objects.List(map.currentLevel))
            {
                if (obj.tile == -1) continue;
                if (obj.tile != prevTile) prevBlock = 3;

                bool draw = false; 

                int tileX, tileY, mapX, mapY;
                obj.GetHexXY(out tileX, out tileY);
                Utilites.GetHexWorldCoord(tileX, tileY, out mapX, out mapY);

                if (map.ShowObjects(Main.ShowTypes.HexGrid) && obj.tile != prevTile) {
                    frmHex = FrmFiles.GetFRM(Object.Types.Interface, (int)Main.ShowTypes.HexGridObj);

                    int wH = frmHex.GetWidth(0, 0);
                    x = mapX - map.cWorldX - (wH >> 1) + frmHex.GetXOffDir(0);

                    if ((x + wH) >= 0 && x < MapTab.cMapViewPortW) {
                        int hH = frmHex.GetHeight(0, 0);
                        y = mapY - map.cWorldY - hH + frmHex.GetYOffDir(0);

                        if ((y + hH) >= 0 && y < MapTab.cMapViewPortH) draw = true;
                    }
                }

                // блокирующий HEX
                if (map.ShowObjects(Main.ShowTypes.HexBlock) && (obj.flags & 0x00000010) == 0) { // _NoBlock
                    int block = 1;

                    int index = (int)Main.ShowTypes.HexBlock;
                    if ((obj.flags & 0x80000000) != 0 || (obj.flags & 0x20000000) !=0) {
                        index = (int)Main.ShowTypes.HexBlockThru;
                        block = 2;
                    }

                    if (block >= prevBlock && obj.tile == prevTile)
                        continue; // уже было отрисовано

                    prevBlock = block;

                    FrmImage frm = FrmFiles.GetFRM(Object.Types.Interface, index);

                    int w = frm.GetWidth(0, 0);
                    int x1 = mapX - map.cWorldX - (w >> 1) + frm.GetXOffDir(0);

                    if ((x1 + w) >= 0 && x1 < MapTab.cMapViewPortW) {
                        int h = frm.GetHeight(0, 0);
                        int y1 = mapY - map.cWorldY - h + frm.GetYOffDir(0);

                        if ((y1 + h) >= 0 && y1 < MapTab.cMapViewPortH) {
                            draw = true;
                            x = x1;
                            y = y1;
                            frmHex = frm;
                        }
                    }
                }
                if (draw) DrawSprite(frmHex, 0, 0, x, y, surface); // рисуем

                prevTile = obj.tile;
            }
        }

        private static void RedrawObjects(MapTab map, Surface surface, int flatType)
        {
            foreach (var obj in map.editedMap.Objects.List(map.currentLevel))
            {
                if (obj.tile == -1 || !map.ShowObjects((Main.ShowTypes)obj.GetFidType())) continue;
                
                // для плоских не ресуем не плоские    
                if ((flatType >= 1 && !obj.IsFlat()) || (flatType == 0 && obj.IsFlat())) continue;
                
                if (flatType == 1 && !obj.IsItem() && obj.IsFlat()) continue; // не рисуем если - объект не предмет и плоский
                if (flatType == 2 &&  obj.IsItem() && obj.IsFlat()) continue; // для плоских объектов - не рисуем спрайты плоских предметов 

                int tileX, tileY, mapX, mapY;
                obj.GetHexXY(out tileX, out tileY);

                Utilites.GetHexWorldCoord(tileX, tileY, out mapX, out mapY);

                FrmImage frm = FrmFiles.GetFRM(obj.fid);

                int w = frm.GetWidth(obj.rotation, obj.frame);
                int x = mapX - map.cWorldX - (w >> 1) + frm.GetXOffDir(obj.rotation) + obj.x;

                if ((x + w) < 0 || x >= MapTab.cMapViewPortW) continue;

                int h = frm.GetHeight(obj.rotation, obj.frame);
                int y = mapY - map.cWorldY - h + frm.GetYOffDir(obj.rotation) + obj.y;

                if ((y + h) >= 0 && y < MapTab.cMapViewPortH) {
                    DrawSprite(frm, obj.rotation, obj.frame, x, y, surface); // рисуем спрайт объекта

                    // выделение объекта
                    if (obj.selected) {
                        DrawSelectedBorder(x, y, w, h, Color.Cyan, 1, surface);
                    } 
                }
            }
        }

        private static void DrawPickObject(MapTab map, Surface surface)
        {
            int x, y;

            Proto.Proto proto = ProtoFiles.GetProto(map.pickObjectPid);
            FrmImage frm = FrmFiles.GetFRM(proto.common.FID);
            
            if ((Object.Types)(map.pickObjectPid >> 24) == Object.Types.Tiles) {
                x = map.placementObjX - map.cWorldX;
                y = map.placementObjY - map.cWorldY;
            } else  {
                int w = frm.GetWidth(0, 0);
                int h = frm.GetHeight(0, 0);

                x = map.placementObjX - map.cWorldX - (w >> 1) + frm.GetXOffDir(0);
                y = map.placementObjY - map.cWorldY - h + frm.GetYOffDir(0);
            }
            DrawSprite(frm, 0, 0, x, y, surface); // рисуем спрайт объекта
        }

        // нарисовать выделение для плитки
        public static void DrawTilePoligon(int tileX, int tileY, MapTab map, Surface surface)
        {
            DrawPoligon(tileX, tileY, tileX, tileY, Color.Silver, 1, surface, map.cWorldX, map.cWorldY);
        }

        public static void DrawMinimap(MapTab map)
        {
            map.MiniMap.Lock(map.currentLevel - 1);
            int totalTiles = LstFiles.FrmTotalCount(Object.Types.Tiles);

            for (int y = 0; y < 100; y++)
            {
                for (int x = 0; x < 100; x++)
                {
                    int index = map.editedMap.Tiles.GetFloorID(x, y, map.currentLevel);
                    index &= 0x3FFF;
                    if (index > 1 && index < totalTiles) {
                        FrmImage frm = FrmFiles.GetFRM(Object.Types.Tiles, index);

                        map.MiniMap.SetPixel(x, y, frm.GetCenterPixelColor());
                    }
                }
            }
            map.MiniMap.Unlock(map.currentLevel - 1);
        }
    }
}
